package files_handler;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.*;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;

import serverDB_classes.DBManager;
import sun.misc.BASE64Decoder;

/**
 * this servlet deals with all files operations requests on server
 * 
 * @author Yoav
 * 
 */
public class FileServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;

	private final int BUF_SIZE = 4096;

	private final String fileFormat = "file";

	private String TMP_DIR = "C:\\temp";

	/**
	 * d.cont
	 */
	public void init() {
	}

	/*
	 * add or update a file on server
	 * 
	 * @param request - http request	 
	 * @param response - http response 
	 * @param userName - user's name who requested the action
	 * 
	 */
	private void addUpdate(HttpServletRequest request,
			HttpServletResponse response, String userName) {
		try {
			// check for multipat
			boolean isMultipart = ServletFileUpload.isMultipartContent(request);

			if (isMultipart) {

				response.setContentType("text/html");
				
				DiskFileItemFactory factory = new DiskFileItemFactory();
				// Create a new file upload handler
				ServletFileUpload upload = new ServletFileUpload(factory);

				//get file items
				List<FileItem> fileItems = upload.parseRequest(request);
				//map file items to a hash map
				Map<String, FileItem> param = convertToHashMap(fileItems);
				//get file's path
				String filePath = param.get("filePath").getString();

				String checkSum = null;
				//check if request has a file
				if (param.containsKey(fileFormat)) {
					// file included
					//write file on server and get its checkSum
					checkSum = FilesManager.getFilesManager().saveFile(
							new File(writeTempFile(param.get(fileFormat)
									.getInputStream())));
				} 
				else {
					if (param.containsKey("checkSum")) {
						// CheckSum only, file already on server
						checkSum = param.get("checkSum").getString();
					} 
					else {
						// no checkSum - new folder
						DBManager.getDBManager().newDir(userName, filePath);

					}
				}
				if (checkSum != null && filePath != null) {
					//insert new file to data base
					DBManager.getDBManager().newFile(checkSum, filePath,
							userName);
				}
			}

		} catch (FileUploadException e) {			
		} catch (IOException e) {			
		}
	}

	/*
	 * put all items in hash map 
	 * @param fileItems - given fileItems list
	 * @return hash map with mapped items
	 */
	private Map<String, FileItem> convertToHashMap(List<FileItem> fileItems) {
		Map<String, FileItem> paramMap = new HashMap<String, FileItem>();
		//go over fileItems
		for (FileItem item : fileItems) {
			//put file items in map according to their field name
			paramMap.put(item.getFieldName(), item);
		}
		return paramMap;
	}

	/*
	 * check if file exists on server according to its checkSum 
	 * @param request - http request
	 * @param response - http response
	 * @param checkSum - file's checkSum
	 */
	private void chkExists(HttpServletRequest request,
			HttpServletResponse response, String checkSum) {
		//check data base if file's checkSum exists
		String answer = FilesManager.getFilesManager().isExists(checkSum) ? "true"
				: "false";
		try {
			//write base the answer
			response.getWriter().print(answer);
		} catch (IOException e) {			
		}
	}

	/*
	 * rename a file or folder on server
	 * @param request - http request	 
	 * @param response - http response 
	 * @param userName - user's name who requested the action
	 */
	private void rename(HttpServletRequest request,
			HttpServletResponse response, String userName) {
		
		String oldPath = request.getParameter("oldPath");
		String newPath = request.getParameter("newPath");

		//check if dir name was changed
		if (DBManager.getDBManager().isDir(oldPath, userName)) {
			//dir's name was changed
			DBManager.getDBManager().renameDir(userName, oldPath, newPath);
		} else {
			//file name was changed
			DBManager.getDBManager().renameFile(userName, oldPath, newPath);
		}
	}

	/*
	 * delete a file or a dir from server
	 * @param request - http request	 
	 * @param response - http response 
	 * @param userName - user's name who requested the action
	 */
	private void delete(HttpServletRequest request,
			HttpServletResponse response, String userName) {
		
		String path = request.getParameter("filePath");

		//check if given path is a dir
		if (DBManager.getDBManager().isDir(path, userName)) {
			//delete dir from data base
			DBManager.getDBManager().deleteDir(path, userName);
		} else {
			//delete file from data base
			String checkSum = DBManager.getDBManager().deleteFile(path,userName);
			//check if deleted file got no more refs
			if (checkSum != null && 0 == DBManager.getDBManager()
							.getChkSumRefCount(checkSum)) {
				//delete file from server
				FilesManager.getFilesManager().deleteFile(checkSum);
			}
		}

	}

	/*
	 * send a file TO client FROM server
	 * @param request - http request	 
	 * @param response - http response 
	 */
	private void sendFile(HttpServletRequest request,
			HttpServletResponse response) {

		
		//get file's checksum
		String checkSum = request.getParameter("checkSum");
		//get file by its checkSum		
		File file = FilesManager.getFilesManager().getFile(checkSum);
		
		DataInputStream in = null;
		ServletOutputStream out = null;

		//set Headers
		ServletContext context = getServletConfig().getServletContext();
		String mimetype = context.getMimeType(file.getName());

		response.setContentType((mimetype != null) ? mimetype
				: "application/octet-stream");
		response.setContentLength((int) file.length());
		response.setHeader("Content-Disposition", "attachment; filename=\""
				+ "fileName" + "\"");
		
		try {
			//file in stream
			in = new DataInputStream(new FileInputStream(file));
			//client out strean
			out = response.getOutputStream();

			//how many bytes were read from file
			int bytesRead = 0;
			//buffer
			byte[] buffer = new byte[BUF_SIZE];
			//read from file and write to client
			while ((in != null) && ((bytesRead = in.read(buffer)) != -1)) {
				out.write(buffer, 0, bytesRead);
			}
		} catch (IOException e) {
		} finally {
			try {
				//close in and out streams
				if (in != null) {
					in.close();
				}
				if (out != null) {
					out.flush();
					out.close();
				}
			} catch (IOException e) {
			}
		}

	}

	/*
	 * handles requests to servlet according to end of URI
	 * @param request - http request	 
	 * @param response - http response 
	 */
	private void commonHandler(HttpServletRequest request,
			HttpServletResponse response) {
		//authenticate connection
		String userName = authenticate(request);
		//check if authentication was successful
		if (null != userName) {
			//check end of URI according to desired request
			
			if (request.getRequestURI().contains("/AddUpdate")) {
				addUpdate(request, response, userName);
				
			} else if (request.getRequestURI().contains("/ChkExists")) {
				chkExists(request, response, request.getParameter("checkSum"));
				
			} else if (request.getRequestURI().contains("/Delete")) {
				delete(request, response, userName);
				
			} else if (request.getRequestURI().contains("/Download")) {				
				sendFile(request, response);
				
			} else if (request.getRequestURI().contains("/Rename")) {
				rename(request, response, userName);
			}
		}
	}
	
	/**
	 * do post method that servlet implements
	 */
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, java.io.IOException {
		//handle request
		commonHandler(request, response);
	}

	/*
	 * write file from client to a temp file on server 
	 * @param fileInStream client's input stream
	 * @return temp file path
	 */
	private String writeTempFile(InputStream fileInStream) {
		//use to set the file's name in case already exists
		int index = 1;
		File file = null;
		//temp file path
		String filePath;
		//make a temp file
		do {
			//set temp file path
			filePath = TMP_DIR + "\\tmp_file_" + index;
			file = new File(filePath);
			index++;
		} while (file.exists());
		
		BufferedInputStream inputStream = new BufferedInputStream(fileInStream);
		FileOutputStream fileOutputStream = null;
		//start writing to temp file
		try {
			fileOutputStream = new FileOutputStream(file);
			int byteRead;
			byte[] buffer = new byte[BUF_SIZE];
			while ((byteRead = inputStream.read(buffer)) != -1) {
				fileOutputStream.write(buffer, 0, byteRead);
			}
		} catch (IOException e) {
		} finally {
			try {
				//close in and out streams
				inputStream.close();
				fileOutputStream.close();
				fileInStream.close();
			} catch (IOException e) {
			}
		}
		return filePath;
	}

	/**
	 * do get method that servlet implements
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, java.io.IOException {
		commonHandler(request, response);
	}

	/**
	 * This method returns userName if the HttpServletRequest contains a valid
	 * Authorization header
	 * 
	 * @param req The HttpServletRequest
	 * @return user's name if the Authorization header is valid, null OW
	 **/

	private String authenticate(HttpServletRequest req){
		//get session
		HttpSession session = req.getSession();

		//get authorization header
		String authHead = req.getHeader("Authorization");

		if (authHead != null) {
			// *****Decode the authorization String*****
			BASE64Decoder decoder = new BASE64Decoder();
			//holds decoded user name and password
			String userNpass = null;
			try {
				userNpass = new String(decoder.decodeBuffer(authHead
						.substring(6)));
			} catch (IOException e) {
			}
			
			// *****Split the userName from the password*****
			String userName = userNpass.substring(0,userNpass.indexOf(":"));
			String password = userNpass.substring(userNpass.indexOf(":") + 1);

			//verify user in data base
			if (DBManager.getDBManager().verifyUser(userName, password)) {
				//varified
				return userName;
			}
		} 
		else {
			String userName = (String) session.getAttribute("userName");
			return userName;
		}
		return null;		
	}
}
